class Compiler {
    constructor(vm) {
        //保存传入的el
        this.el = vm.$el;
        //保存传入的vm实例
        this.vm = vm;

        this.compile(this.el)
    }
    //此方法是为了编译模板,处理文本节点和元素节点
    compile(el) {
        //保存传入的dom元素
        let childNodes = el.childNodes;
        //默认是一个伪数组, 通过array.from方法转换成数组, 在调用forEach方法
        Array.from(childNodes).forEach(node => {
            //判断节点类型是否为文本节点
            if (this.isTextNode(node)) {
                this.compileText(node)
                //逻辑判断是否为元素节点
            } else if (this.isElementNode(node)) {
                this.compileElement(node)
            }
            //判断node,是否有子节点,如果有子节点,要递归调用compile方法
            if (node.childNodes && node.childNodes.length) {
                this.compile(node);
            }
        })
    }

    //编译元素节点,处理指令
    compileElement(node) {
        //获取所有的属性节点
        //遍历所有的属性节点,判断是是否有指令
        Array.from(node.attributes).forEach(attr => {
            //判断属性是否有指令
            let attrName = attr.name
            if (this.isDirective(attrName)) {
                //v-text --> text  把v-text转换成为text
                //从第三个开始截取
                attrName = attrName.substr(2);
                //保留属性的值
                let key = attr.value;

                this.update(node, key, attrName)
            }
        })
    }
    //把指令和事件进行组合 这个方法有3个参数,node,key,attrName
    //node是dom元素,key是data中属性的名字,也就是指令的名字
    update(node, key, attrName) {
        let updateFn = this[attrName + "Updater"];
        updateFn && updateFn.call(this, node, this.vm[key], key);

    }

    //处理v-text指令 把对应的值取出来赋值给对应的dom元素
    //第一个参数是dom节点,第二个参数是value都所对应的数据
    textUpdater(node, value, key) {
        node.textContent = value
        new Watcher(this.vm, key, (newValue) => {
            node.textContent = newValue;
        })
    }
    //处理v-model指令
    modelUpdater(node, value, key) {
        node.value = value
        new Watcher(this.vm, key, (newValue) => {
            node.value = newValue;
        });

        addEventListener('input', () => {
            this.vm[key] = node.value
        })

    }
    //编译文本节点,处理差值表达式
    compileText(node) {

        //定义正则表达式,匹配差值表达式
        let reg = /\{\{(.+?)\}\}/
        //定义一个值保存当前文本节点的内容
        let value = node.textContent;
        //逻辑判断文本节点的内容是否能够匹配正则表达式
        if (reg.test(value)) {
            //获取正则中第一个匹配的内容,并且去空格
            let key = RegExp.$1.trim();
            //替换文本内容,大括号中的变量替换data里面的变量
            node.textContent = value.replace(reg, this.vm[key]);
            //watcher 监视值的变化并且更新视图
            new Watcher(this.vm, key, (newValue) => {
                node.textContent = newValue
            })
        }
    }

    //判断元素属性是否有指令
    isDirective(attrName) {
        return attrName.startsWith('v-')
    }

    //判断节点是不是文本节点
    isTextNode(node) {
        return node.nodeType === 3
    }

    //判断节点是不是元素节点
    isElementNode(node) {
        return node.nodeType === 1
    }
}